#!/usr/bin/env python3
import socket
import threading
import logging
import tkinter as tk
from tkinter import scrolledtext, simpledialog, font as tkFont
import queue
import asyncio
import websockets
from websockets.exceptions import ConnectionClosedOK, ConnectionClosedError

# --- 配置 ---
HOST = '0.0.0.0'  # 监听所有网络接口
PORT = 8080
MAX_HISTORY = 50
LOG_FORMAT = '%(asctime)s - %(levelname)s - %(threadName)s - %(message)s'

# --- 日志设置 ---
logging.basicConfig(level=logging.INFO, format=LOG_FORMAT)

# --- 全局变量 ---
CLIENTS = set()
CLIENTS_LOCK = asyncio.Lock()
MESSAGE_HISTORY = []
MESSAGE_QUEUE = queue.Queue()
GUI_ROOT = None
ASYNCIO_LOOP = None
SERVER_TASK = None

# --- GUI 颜色和字体 --- (新增部分)
BG_COLOR = "#F0F0F0"  # 主背景色
TEXT_AREA_BG = "#FFFFFF"  # 文本区域背景
INPUT_BG = "#FFFFFF"  # 输入框背景
SYSTEM_COLOR = "#606060"  # 系统消息颜色 (灰色)
ADMIN_COLOR = "#0000FF"  # 管理员消息颜色 (蓝色)
USER_COLOR = "#000000"  # 用户消息颜色 (黑色)
BUTTON_COLOR = "#E1E1E1"  # 按钮颜色
DEFAULT_FONT = ("Segoe UI", 10)  # 默认字体 (Windows)


# 如果在非Windows系统，可以尝试 "Arial" 或 "Helvetica"
# DEFAULT_FONT = ("Arial", 10)

# --- 网络功能函数 --- (Now async)

async def broadcast_message(message, source="系统", exclude_websocket=None):
    """格式化消息、记录日志、加入GUI队列，并异步发送给WebSocket客户端。"""
    # **防御性编程：确保 message 和 source 不为 None**
    if message is None:
        logging.error("broadcast_message 收到 None 类型的 message，已忽略")
        return
    if source is None:
        source = "未知来源"  # 提供默认值
        logging.warning("broadcast_message 收到 None 类型的 source，已使用默认值")

    # **确保格式化后的消息是有效字符串**
    try:
        formatted_message = f"{source}: {message}"
        if not formatted_message:
            logging.error(f"广播消息格式化后为空: source='{source}', message='{message}'")
            return
    except Exception as format_err:
        logging.error(f"格式化广播消息时出错: {format_err}, source='{source}', message='{message}'")
        return

    # 1. 记录日志
    logging.info(f"广播: {formatted_message}")

    # 2. 添加到服务器GUI队列
    MESSAGE_QUEUE.put(formatted_message)  # Keep using the queue for GUI

    # 3. 添加到历史记录 (确保添加有效消息)
    async with CLIENTS_LOCK:
        MESSAGE_HISTORY.append(formatted_message + '\n')
        if len(MESSAGE_HISTORY) > MAX_HISTORY:
            MESSAGE_HISTORY.pop(0)

    # 4. 异步发送给所有连接的客户端
    if CLIENTS:
        # logging.info(f"正在向 {len(CLIENTS)} 个客户端发送消息") # 注释掉，减少日志干扰
        tasks = []
        async with CLIENTS_LOCK:
            for client in CLIENTS:
                if client != exclude_websocket:
                    try:
                        msg_to_send_str = str(formatted_message)
                        if not msg_to_send_str:
                            logging.warning(f"广播：尝试向客户端发送空消息，已跳过。原始: {formatted_message}")
                            continue

                        # !!!!! 修改点：编码为 bytes !!!!!
                        try:
                            msg_to_send_bytes = msg_to_send_str.encode('utf-8')
                        except Exception as encode_err:
                            logging.error(f"广播消息编码失败: {encode_err}, 原始字符串: '{msg_to_send_str}'")
                            continue  # 编码失败则不发送

                        logging.critical(
                            f">>> BROADCAST SENDING (BYTES) >>> Length: {len(msg_to_send_bytes)}, String: '{msg_to_send_str}'")
                        tasks.append(asyncio.create_task(client.send(msg_to_send_bytes)))
                    except Exception as send_prep_err:
                        logging.error(f"准备发送广播消息时出错: {send_prep_err}")

            if tasks:
                done, pending = await asyncio.wait(tasks, return_when=asyncio.ALL_COMPLETED)

                for task in done:
                    try:
                        task.result()  # Check for exceptions during send
                        # logging.info("消息发送成功") # 日志过于频繁，可注释掉
                    except (ConnectionClosedOK, ConnectionClosedError, websockets.exceptions.WebSocketException) as e:
                        logging.warning(f"发送广播消息时连接已关闭或出错: {e}")
                    except Exception as e:
                        logging.error(f"处理广播发送结果时发生未知错误: {e}")


async def handle_connection(websocket, path=None, *args, **kwargs):
    """处理单个WebSocket连接。"""
    client_address = websocket.remote_address
    client_name = f"用户 {client_address[0]}:{client_address[1]}" if client_address else "未知用户"
    logging.info(f"WebSocket 连接自: {client_name}")

    # Register client
    async with CLIENTS_LOCK:
        CLIENTS.add(websocket)
        logging.info(f"客户端 {client_name} 已注册。当前客户端数量: {len(CLIENTS)}")

    # !!!!! 添加延迟 !!!!!
    await asyncio.sleep(0.5)  # 延迟 500 毫秒
    logging.info(f"延迟结束，开始向 {client_name} 发送初始消息。")

    # 发送硬编码测试消息 (也用 bytes)
    try:
        test_msg_str = "SERVER_CONNECTION_TEST_MESSAGE_BINARY_DELAYED"
        test_msg_bytes = test_msg_str.encode('utf-8')
        logging.critical(f">>> SENDING DELAYED TEST MESSAGE (BYTES) >>> String: '{test_msg_str}'")
        await websocket.send(test_msg_bytes)
        logging.info("延迟后的硬编码二进制测试消息已发送。")
    except Exception as test_send_err:
        logging.error(f"发送延迟后的硬编码测试消息失败: {test_send_err}")

    try:
        # 发送欢迎和历史消息 (也用 bytes)
        welcome_msg_str = "系统: 欢迎加入聊天室!"
        if welcome_msg_str:
            welcome_msg_bytes = welcome_msg_str.encode('utf-8')
            logging.critical(f">>> WELCOME SENDING (BYTES) >>> String: '{welcome_msg_str}'")
            await websocket.send(welcome_msg_bytes)

        async with CLIENTS_LOCK:
            # 获取当前历史记录副本，避免长时间持有锁
            current_history = list(MESSAGE_HISTORY)

        if current_history:
            history_intro_str = "系统: 以下是最近的消息记录:"
            if history_intro_str:
                history_intro_bytes = history_intro_str.encode('utf-8')
                logging.critical(f">>> HISTORY INTRO SENDING (BYTES) >>> String: '{history_intro_str}'")
                await websocket.send(history_intro_bytes)

            logging.info(f"准备发送 {len(current_history)} 条历史消息给 {client_name}")
            for msg in current_history:
                try:
                    clean_msg_str = msg.strip()
                    if clean_msg_str:
                        clean_msg_bytes = clean_msg_str.encode('utf-8')
                        # logging.critical(f">>> HISTORY ITEM SENDING (BYTES) >>> String: '{clean_msg_str}'") # 减少日志
                        await websocket.send(clean_msg_bytes)
                except Exception as hist_send_err:
                    logging.error(f"发送历史消息 '{clean_msg_str}' 时出错: {hist_send_err}")
            logging.info(f"历史消息发送完毕给 {client_name}")

        # 广播用户加入 (broadcast_message 内部已处理编码)
        join_msg = f"{client_name} 加入了聊天室"
        await broadcast_message(join_msg, source="系统", exclude_websocket=websocket)

        # 监听客户端消息循环 (接收逻辑保持不变)
        async for message in websocket:
            # ... (接收和解码逻辑不变, 确保能处理 bytes 或 str) ...
            if isinstance(message, bytes):
                try:
                    message_str = message.decode('utf-8')
                except UnicodeDecodeError:
                    logging.error(f"无法解码来自 {client_name} 的 bytes 消息")
                    continue
            elif isinstance(message, str):
                message_str = message
            else:
                logging.warning(f"收到未知类型的消息 {type(message)}，尝试转字符串")
                message_str = str(message)

            if not message_str or not message_str.strip():
                continue
            await broadcast_message(message_str, source=client_name, exclude_websocket=websocket)

    except (ConnectionClosedOK, ConnectionClosedError):
        logging.info(f"WebSocket 连接已由 {client_name} 关闭。")
    except Exception as e:
        logging.error(f"处理 {client_name} 时发生错误: {e}", exc_info=True)
    finally:
        logging.info(f"清理 {client_name} 的连接")
        # Unregister client
        async with CLIENTS_LOCK:
            if websocket in CLIENTS:
                CLIENTS.remove(websocket)
                # Announce user left
                if ASYNCIO_LOOP and ASYNCIO_LOOP.is_running():
                    leave_msg = f"{client_name} 离开了聊天室"
                    logging.info(f"广播用户离开消息: 系统: {leave_msg}")
                    asyncio.run_coroutine_threadsafe(
                        broadcast_message(leave_msg, source="系统"),
                        ASYNCIO_LOOP
                    )
                else:
                    logging.warning("Asyncio 循环未运行，无法广播离开消息")
        logging.info(f"为 {client_name} 的连接处理程序已结束")


# --- GUI 功能函数 ---

def update_chat_display():
    """检查消息队列并使用标签更新Tkinter Text控件。"""
    try:
        while True:
            message = MESSAGE_QUEUE.get_nowait()
            # **检查从队列获取的消息**
            if message is None:
                logging.warning("从 MESSAGE_QUEUE 获取到 None，已忽略")
                continue
            message_str = str(message)
            if not message_str:
                logging.warning("从 MESSAGE_QUEUE 获取到空字符串，已忽略")
                continue

            if GUI_ROOT and chat_display:
                chat_display.config(state=tk.NORMAL)

                # 根据消息来源确定标签
                tag = "user_tag"  # 默认用户标签
                if message_str.startswith("系统:"):
                    tag = "system_tag"
                elif message_str.startswith("管理员:"):
                    tag = "admin_tag"

                # 插入带标签的消息
                chat_display.insert(tk.END, message_str + '\n', tag)

                chat_display.yview(tk.END)
                chat_display.config(state=tk.DISABLED)
    except queue.Empty:
        pass
    except Exception as e:
        logging.error(f"更新GUI时出错: {e}")
    finally:
        if GUI_ROOT and GUI_ROOT.winfo_exists():
            GUI_ROOT.after(150, update_chat_display)


def send_admin_message(event=None):
    """从管理员输入获取消息并通过asyncio事件循环广播。"""
    message = input_entry.get().strip()
    # **检查管理员输入**
    if not message:
        logging.warning("管理员尝试发送空消息，已忽略")
        return

    if message is None:  # 理论上 Tkinter get() 不会返回 None，但以防万一
        logging.error("管理员输入获取到 None 值，已忽略")
        return

    if ASYNCIO_LOOP and ASYNCIO_LOOP.is_running():
        # 直接在消息队列添加管理员消息（为了在服务器GUI显示）
        gui_admin_msg = f"管理员: {message}"
        MESSAGE_QUEUE.put(gui_admin_msg)
        logging.info(f"管理员输入消息: {message}")

        # 以最简单的方式向所有客户端发送消息
        async def send_to_all():
            try:
                # 记录当前客户端数量
                client_count = 0
                async with CLIENTS_LOCK:
                    client_count = len(CLIENTS)
                # logging.info(f"准备向 {client_count} 个客户端发送管理员消息") # 注释掉减少干扰
                if client_count == 0:
                    return

                # 构建简单的纯文本消息
                admin_message_str = f"管理员: {message}"
                # **确保 admin_message 是有效字符串**
                if not admin_message_str:
                    logging.error(f"格式化后的管理员消息为空: '{message}'")
                    return

                # !!!!! 修改点：编码为 bytes !!!!!
                try:
                    admin_message_bytes = admin_message_str.encode('utf-8')
                except Exception as encode_err:
                    logging.error(f"管理员消息编码失败: {encode_err}, 原始字符串: '{admin_message_str}'")
                    return  # 编码失败则不发送

                # logging.info(f"格式化的管理员消息: '{admin_message_str}' (类型: {type(admin_message_str)})") # 注释掉减少干扰

                # 逐个发送给所有客户端
                async with CLIENTS_LOCK:
                    for client in CLIENTS:
                        try:
                            # **发送前最后检查**
                            # 检查 bytes 是否为空 (理论上不会)
                            if not admin_message_bytes:
                                logging.warning(f"管理员：尝试向客户端发送空 bytes，已跳过。原始 str: {admin_message_str}")
                                continue

                            logging.critical(
                                f">>> ADMIN SENDING (BYTES) >>> Length: {len(admin_message_bytes)}, String: '{admin_message_str}'")
                            await client.send(admin_message_bytes)
                        except Exception as e:
                            logging.error(f"向客户端 {client.remote_address} 发送管理员消息失败: {e}")
            except Exception as e:
                logging.error(f"管理员消息发送过程中发生错误: {e}")

        # 使用更直接的协程调度方式并等待完成
        try:
            future = asyncio.run_coroutine_threadsafe(send_to_all(), ASYNCIO_LOOP)
            # 可选：等待发送完成（最多1秒）
            future.result(1.0)
            # logging.info("管理员消息发送协程已完成") # 注释掉减少干扰
        except Exception as e:
            logging.error(f"管理员消息发送调度或执行出错: {e}")

        # 添加到历史记录 (确保添加有效消息)
        history_admin_msg = f"管理员: {message}"
        if history_admin_msg:
            asyncio.run_coroutine_threadsafe(add_to_history(history_admin_msg), ASYNCIO_LOOP)
    else:
        logging.error("Asyncio loop is not running. Cannot send admin message.")
    input_entry.delete(0, tk.END)


# 新增：单独处理历史记录逻辑
async def add_to_history(message):
    """添加消息到历史记录"""
    # **检查传入的消息**
    if message is None:
        logging.warning("尝试向历史记录添加 None，已忽略")
        return
    message_str = str(message)
    if not message_str:
        logging.warning("尝试向历史记录添加空字符串，已忽略")
        return

    async with CLIENTS_LOCK:
        # 添加带换行符的消息
        MESSAGE_HISTORY.append(message_str + '\n')
        if len(MESSAGE_HISTORY) > MAX_HISTORY:
            MESSAGE_HISTORY.pop(0)


def on_closing():
    """处理GUI窗口关闭事件，并尝试优雅地停止服务器。"""
    logging.info("GUI窗口正在关闭... 尝试停止服务器...")
    global SERVER_TASK, ASYNCIO_LOOP

    if SERVER_TASK and not SERVER_TASK.done():
        logging.info("正在取消服务器任务...")
        SERVER_TASK.cancel()

    # Attempt to stop the loop if it's running from another thread
    # This might not be strictly necessary if the thread daemonizes, but good practice
    if ASYNCIO_LOOP and ASYNCIO_LOOP.is_running():
        logging.info("正在停止 asyncio 事件循环...")
        # 使用 stop() 可能导致未完成的任务出错，更好的方式是等待任务取消
        # ASYNCIO_LOOP.call_soon_threadsafe(ASYNCIO_LOOP.stop)
        # 尝试更优雅地关闭
        pass  # 取消任务后，循环通常会自动停止

    if GUI_ROOT:
        GUI_ROOT.destroy()
    logging.info("GUI已销毁。")


def create_gui():
    """创建并美化主Tkinter GUI窗口。"""
    global GUI_ROOT, chat_display, input_entry

    GUI_ROOT = tk.Tk()
    GUI_ROOT.title("服务器聊天监控")
    GUI_ROOT.configure(bg=BG_COLOR)
    GUI_ROOT.protocol("WM_DELETE_WINDOW", on_closing)

    # 设置默认字体
    default_font = tkFont.nametofont("TkDefaultFont")
    default_font.configure(family=DEFAULT_FONT[0], size=DEFAULT_FONT[1])
    GUI_ROOT.option_add("*Font", default_font)

    # 聊天显示区域 (美化)
    text_frame = tk.Frame(GUI_ROOT, bd=1, relief=tk.SUNKEN)
    text_frame.pack(padx=10, pady=10, fill=tk.BOTH, expand=True)

    chat_display = scrolledtext.ScrolledText(text_frame, state=tk.DISABLED, wrap=tk.WORD,
                                             bg=TEXT_AREA_BG, bd=0,
                                             padx=5, pady=5,
                                             font=DEFAULT_FONT)
    chat_display.pack(fill=tk.BOTH, expand=True)

    # 配置消息标签颜色 (新增)
    chat_display.tag_configure("system_tag", foreground=SYSTEM_COLOR)
    chat_display.tag_configure("admin_tag", foreground=ADMIN_COLOR, font=(DEFAULT_FONT[0], DEFAULT_FONT[1], "bold"))
    chat_display.tag_configure("user_tag", foreground=USER_COLOR)

    # 输入框架 (美化)
    input_frame = tk.Frame(GUI_ROOT, bg=BG_COLOR)
    input_frame.pack(fill=tk.X, padx=10, pady=(0, 10))

    input_entry = tk.Entry(input_frame, bg=INPUT_BG, bd=2, relief=tk.GROOVE,
                           font=DEFAULT_FONT, width=60)
    input_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, ipady=5, padx=(0, 5))
    input_entry.bind("<Return>", send_admin_message)

    send_button = tk.Button(input_frame, text="发送", command=send_admin_message,
                            bg=BUTTON_COLOR, relief=tk.RAISED, bd=2, padx=10,
                            font=(DEFAULT_FONT[0], DEFAULT_FONT[1], "bold"))
    send_button.pack(side=tk.RIGHT)

    GUI_ROOT.after(100, update_chat_display)
    return GUI_ROOT


# --- 主执行逻辑 ---

async def start_server():
    """启动WebSocket服务器并保持运行。"""
    global ASYNCIO_LOOP, SERVER_TASK
    ASYNCIO_LOOP = asyncio.get_running_loop()

    logging.info(f"尝试在 {HOST}:{PORT} 启动WebSocket服务器...")
    try:
        # Use websockets.serve
        async with websockets.serve(handle_connection, HOST, PORT) as server:
            logging.info(f"WebSocket服务器正在运行于 {server.sockets[0].getsockname()}")

            # Keep the server running until cancelled
            # Store the server task so it can be cancelled
            SERVER_TASK = asyncio.current_task()
            # 等待直到被取消
            await asyncio.Future()

    except asyncio.CancelledError:
        logging.info("服务器任务已被取消。正在关闭...")
    except OSError as e:
        logging.critical(f"无法启动服务器：{e} - 端口 {PORT} 可能已被占用。")
        MESSAGE_QUEUE.put(f"系统: 致命错误 - 无法在端口 {PORT} 启动服务器。{e}")
    except Exception as e:
        logging.critical(f"启动或运行服务器时发生意外错误: {e}", exc_info=True)
        MESSAGE_QUEUE.put(f"系统: 致命错误 - 服务器启动失败。 {e}")
    finally:
        logging.info("服务器协程结束。")


def run_asyncio_server():
    """在单独的线程中运行asyncio事件循环。"""
    try:
        # Python 3.7+ 可以直接用 asyncio.run
        # 对于需要从其他线程访问循环的情况，可能需要更复杂的设置
        # 但在这个场景下，daemon 线程和 asyncio.run 应该足够
        asyncio.run(start_server())
    except Exception as e:
        logging.critical(f"Asyncio 运行线程崩溃: {e}", exc_info=True)
    finally:
        logging.info("Asyncio 线程已结束。")


if __name__ == "__main__":
    server_thread = None
    try:
        # 1. 创建GUI
        root = create_gui()
        logging.info("GUI已创建。")

        # 2. 在单独的线程中启动Asyncio服务器
        # Make the thread a daemon so it exits when the main thread (GUI) exits
        server_thread = threading.Thread(target=run_asyncio_server, name="AsyncioServerThread", daemon=True)
        server_thread.start()
        logging.info("Asyncio 服务器线程已启动。")

        # 3. 启动Tkinter主循环 (在主线程中)
        logging.info("启动Tkinter主循环...")
        root.mainloop()

    except Exception as e:
        logging.critical(f"主线程发生意外错误: {e}", exc_info=True)
    finally:
        # 清理 - on_closing() handles server shutdown initiation
        logging.info("Tkinter mainloop 已结束。程序正在退出。")
        # 服务器关闭和线程退出由 daemon=True 和 on_closing 处理
        logging.info("最终清理完成。")